package com.edfeff.bio.http;

import java.io.IOException;
import java.io.InputStream;

public class InputBuffer {
    protected InputStream inputStream;
    protected Request request;
    protected byte[] buf;//缓冲区,请求流中的数据都需要进入缓冲区后再处理,可以提高性能和降低操作复杂度
    protected int pos;//指向当前buf中准备写入和读取的位置
    protected int lastValid;//buf中最后一个有效的字符,此索引后面都是无效的,为初始化的数据

    public InputBuffer(Request request) {
        this.request = request;
        pos = 0;
        lastValid = 0;
        buf = new byte[48 * 1024];
    }

    public void setInputStream(InputStream inputStream) {
        this.inputStream = inputStream;
    }


    //请求行解析

    /**
     * <pre>
     *     Request-Line   = Method SP Request-URI SP HTTP-Version CRLF
     *
     *     Method         = "OPTIONS"                   ; Section 9.2
     *                       | "GET"                    ; Section 9.3
     *                       | "HEAD"                   ; Section 9.4
     *                       | "POST"                   ; Section 9.5
     *                       | "PUT"                    ; Section 9.6
     *                       | "DELETE"                 ; Section 9.7
     *                       | "TRACE"                  ; Section 9.8
     *                       | "CONNECT"                ; Section 9.9
     *                       | extension-method
     *        extension-method = token
     *
     *     Request-URI    = "*" | absoluteURI | abs_path | authority
     *
     *     HTTP-Version   = "HTTP" "/" 1*DIGIT "." 1*DIGIT
     * </pre>
     */
    public void parseRequestLine() throws IOException {
        //1 Method SP 读取方法名字
        StringBuilder charTemp = new StringBuilder();
        boolean space = false;
        while (!space) {
            //填充buf
            fill();
            if (buf[pos] == Constants.SP) {
                space = true;
                request.setMethod(charTemp.toString());
            } else {
                charTemp.append((char) buf[pos]);
            }
            pos++;
        }

        //2 Request-URI SP 读取uri
        int start = pos;
        space = false;
        int end = 0;
        while (!space) {
            fill();
            if (buf[pos] == Constants.SP) {
                space = true;
                end = pos;
            }
            pos++;
        }
        request.setUri(StrUtils.from(buf, start, end - start));

        //3. HTTP-Version CRLF 读取版本
        // http/1.1 CR LF
        // ↑        ↑   ↑
        //start    end endOfLine
        charTemp = new StringBuilder();
        boolean endOfLine = false;
        while (!endOfLine) {
            fill();
            if (buf[pos] == Constants.CR) {
                //nothing
            } else if (buf[pos] == Constants.LF) {
                //标记请求行结束
                endOfLine = true;
            } else {
                //只添加非空白字符
                charTemp.append((char) buf[pos]);
            }
            pos++;
        }
        request.setProtocol(charTemp.toString());
    }

    /**
     * 请求头解析
     */
    public void parseHeaders()
            throws IOException {
        //逐行解析头部
        while (parseHeader()) ;
    }

    /**
     * 解析一行头部
     * <pre>
     * general-header = Cache-Control               ; Section 14.9
     *                   | Connection               ; Section 14.10
     *                   | Date                     ; Section 14.18
     *                   | Pragma                   ; Section 14.32
     *                   | Trailer                  ; Section 14.40
     *                   | Transfer-Encoding        ; Section 14.41
     *                   | Upgrade                  ; Section 14.42
     *                   | Via                      ; Section 14.45
     *                   | Warning                  ; Section 14.46
     *
     * request-header = Accept             ; Section 14.1
     *          | Accept-Charset           ; Section 14.2
     *          | Accept-Encoding          ; Section 14.3
     *          | Accept-Language          ; Section 14.4
     *          | Authorization            ; Section 14.8
     *          | Expect                   ; Section 14.20
     *          | From                     ; Section 14.22
     *          | Host                     ; Section 14.23
     *          | If-Match                 ; Section 14.24
     *          | If-Modified-Since        ; Section 14.25
     *          | If-None-Match            ; Section 14.26
     *          | If-Range                 ; Section 14.27
     *          | If-Unmodified-Since      ; Section 14.28
     *          | Max-Forwards             ; Section 14.31
     *          | Proxy-Authorization      ; Section 14.34
     *          | Range                    ; Section 14.35
     *          | Referer                  ; Section 14.36
     *          | TE                       ; Section 14.39
     *          | User-Agent               ; Section 14.43
     *
     * entity-header  = Allow              ; Section 14.7
     *          | Content-Encoding         ; Section 14.11
     *          | Content-Language         ; Section 14.12
     *          | Content-Length           ; Section 14.13
     *          | Content-Location         ; Section 14.14
     *          | Content-MD5              ; Section 14.15
     *          | Content-Range            ; Section 14.16
     *          | Content-Type             ; Section 14.17
     *          | Expires                  ; Section 14.21
     *          | Last-Modified            ; Section 14.29
     *          | extension-header
     * </pre>
     */
    private boolean parseHeader() throws IOException {
        //空白符号跳过 和 头部结束判断
        byte chr = 0;
        while (true) {
            fill();
            chr = buf[pos];
            if ((chr == Constants.CR) || (chr == Constants.LF)) {
                if (chr == Constants.LF) {
                    pos++;
                    return false;
                }
            } else {
                break;
            }
            pos++;
        }

        //格式 headerName : SP headerValues
        Header header = new Header();
        //1 解析键
        StringBuilder headerTmp = new StringBuilder();
        //分号标记，区分请求头的键和值
        boolean colon = false;
        while (!colon) {
            // Read new bytes if needed
            fill();
            if (buf[pos] == Constants.COLON) {
                colon = true;
                header.setName(headerTmp.toString());
            } else {
                headerTmp.append(Character.toLowerCase((char) buf[pos]));
            }
            pos++;
        }

        //2 解析值
        // 跳过空白符号 空格、制表符
        boolean space = true;
        while (space) {
            fill();
            if ((buf[pos] == Constants.SP) || (buf[pos] == Constants.HT)) {
                pos++;
            } else {
                space = false;
            }
        }

        // 读取数据到行结束CRLF
        int start = pos;
        int end = 0;
        boolean endOfLine = false;
        headerTmp = new StringBuilder();

        while (!endOfLine) {
            fill();
            if (buf[pos] == Constants.CR) {
                end = pos;
            } else if (buf[pos] == Constants.LF) {
                endOfLine = true;
            }
            pos++;
        }
        headerTmp.append(StrUtils.from(buf, start, end - start));
        header.setValue(headerTmp.toString());
        request.getHeaders().add(header);
        return true;
    }

    public void parseRequestBody() throws IOException {
        //请求体长度
        long contentLength = request.getContentLengthLong();
        if (contentLength <= 0)
            return;
        //读取请求体 包装到byte[]中
        fill();
        int len = (int) contentLength;
        byte[] body = new byte[len];
        System.arraycopy(buf, pos, body, 0, len);
        request.setBody(body);
    }

    /**
     * 填充缓冲区
     *
     * @return 读到流末尾返回false
     * @throws IOException
     */
    protected boolean fill() throws IOException {
        if (pos < lastValid) {
            return false;
        }
        //...pos... -> buf[...]
        //填充缓存区
        int nRead = 0;
        nRead = inputStream.read(buf, pos, buf.length - lastValid);
        if (nRead > 0) {
            lastValid = pos + nRead;
        }
        return nRead > 0;
    }
}
